Skip to content

feat: event-driven extraction — Claude SessionEnd + opencode session.idle#79

Merged
itlackey merged 4 commits into
release/0.9.0from
feat/0.9.0/session-end-extract
Jun 21, 2026
Merged

feat: event-driven extraction — Claude SessionEnd + opencode session.idle#79
itlackey merged 4 commits into
release/0.9.0from
feat/0.9.0/session-end-extract

Conversation

@itlackey

@itlackey itlackey commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Event-driven session extraction for both providers, so durable insights reach the proposal queue in seconds/minutes instead of waiting for the periodic extract cron.

Claude — SessionEnd (one-shot)

  • akm-hook.ts: new extract-session dispatch — reads session_id from the SessionEnd stdin payload, skips transient terminations (clear/resume), spawns extract detached + unref'd.
  • plugin.json: registers extract-session as a second SessionEnd hook.
  • Fires once at the session's true end — ideal.

opencode — session.idle (min-interval gate, Option #3)

opencode has no true session-end event, and session.idle fires after every turn (and each turn changes the content hash, so naive hooking would re-extract every turn). So:

  • On session.idle, extract at most once per AKM_EXTRACT_MIN_INTERVAL_MS (default 10 min) — a turn-burst collapses to one periodic checkpoint.
  • Per-session sessionLastExtractAt map (cleared on session.deleted); detached spawn; configurable via env.
  • Tradeoff: idle-gated fires mid-session (more cost than the Claude one-shot end-capture) but is the cleanest option opencode's events allow without a debounce timer — still a big latency improvement over cron-only. (Trailing-debounce was the more elegant alternative; chosen OpenCode search tool has no ability to search the registry #3 for simplicity/robustness now.)

Shared design

  • Depends on akm-cli 0.9.0-beta.33: extract --session-id respects the content-hash ledger (idempotent; --force overrides), so re-fires + the cron are cheap zero-LLM skips.
  • The periodic extract cron stays the backstop for crashes / final deltas — opencode's dispose/server.instance.disposed are unverified on hard exit, so nothing relies on them.
  • Latency win, not yield: does not change the inherent ~88% no-yield (separate work).

Tests

Claude: plugin.json registration + dispatch (clean no-op w/o akm on PATH; clear-reason skipped). opencode: session.idle → one extract spawn; rapid second idle gated. Full plugin suite green (328, 0 fail).

🤖 Generated with Claude Code

itlackey and others added 2 commits June 21, 2026 13:30
Fire `akm extract --type claude-code --session-id <id>` for the just-ended
session so its durable insights reach the proposal queue in seconds instead of
waiting for the periodic extract cron.

- akm-hook.ts: new `extract-session` dispatch — reads the session_id from the
  SessionEnd stdin payload, skips transient terminations (reason clear/resume),
  and spawns extract detached + unref'd (never blocks session close).
- plugin.json: register `extract-session` as a second SessionEnd hook.

Idempotent + safe by design: `--session-id` respects the content-hash ledger
(akm #602 / beta.33), so a re-fire or the periodic cron is a cheap skip with
zero LLM calls — no `--force`. The cron remains the backstop for crashes that
fire no hook. (opencode session.idle extraction intentionally deferred — needs a
debounce/dedupe strategy to avoid flooding extract on every turn.)

Tests: plugin.json registration + dispatch (clean no-op without akm on PATH;
clear-reason skipped). Full suite green (326 → 328).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… gate)

opencode has no true "session end" event and session.idle fires after every
turn, so naive idle-hooking would re-extract each turn (each turn changes the
content hash → not a cheap ledger skip). Option #3: min-interval gate.

- On session.idle, extract the session at most once per AKM_EXTRACT_MIN_INTERVAL_MS
  (default 10 min) via detached `akm extract --type opencode --session-id <id>`.
  A turn-burst collapses to one periodic checkpoint; the content-hash ledger
  (akm-cli #602 / >=beta.33) further no-ops unchanged content for free.
- Per-session `sessionLastExtractAt` Map (module-scope, long-lived plugin),
  cleared in the session.deleted cleanup. Configurable via env.
- Only on session.idle (not compacted/deleted). Fire-and-forget so it never
  stalls a turn. The periodic extract cron remains the backstop for the final
  delta after the last turn (idle gives periodic checkpoints, not end-capture).

Tradeoff vs the Claude SessionEnd one-shot: idle-gated extraction fires
mid-session (more cost than a single end extract) but is the cleanest option
opencode's event surface allows without a debounce timer — and is still a large
improvement over cron-only latency.

Tests: session.idle → one extract spawn; rapid second idle is gated. Full
plugin suite green (328).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@itlackey itlackey changed the title feat(claude): event-driven extraction on SessionEnd feat: event-driven extraction — Claude SessionEnd + opencode session.idle Jun 21, 2026
itlackey and others added 2 commits June 21, 2026 14:56
…ilures)

`runHookSandboxed` (akm-version-check) and `runHook` (claude-plugin) spread
`...process.env` into the spawned hook, so ambient/CI env leaked in. CI runners
set AKM_PLUGIN_NO_AUTO_DEFAULT=1, which flipped the first-run auto-default OFF —
making the "auto-default-agent write" + "session-start injects curated context"
tests pass locally but fail in CI (the tier2 red blocking PR #79).

Strip inherited AKM_* vars before spawning so the sandbox depends only on what
each test sets. Reproduced + verified: full suite is 328/0 both with and without
AKM_PLUGIN_NO_AUTO_DEFAULT=1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getAkmConfigPath() reads XDG_CONFIG_HOME. CI runners set it to a config
that already has defaults.agent, so the first-run auto-default never fires
— green locally (XDG_CONFIG_HOME unset), red in CI. Strip XDG_* from the
inherited env and pin every XDG base dir to the sandbox tempDir so config
reads are hermetic regardless of the ambient environment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Eval diff

  • Baseline SHA: f5424051799b4e4e2660c511cbbbdc82e42a25a3
  • Candidate SHA: d11e0112eb9f6477dba982d2793472d4635b9e5d

Metric deltas

Metric Key Baseline Candidate Δ Δ% Status
curation mean_expected_coverage 0.9125 0.9125 0 0.00% • unchanged
curation mean_reciprocal_rank 0.9208 0.9208 0 0.00% • unchanged
curation zero_hit_rate 0.0500 0.0500 0 0.00% • unchanged
context_budget claude_violations 0 0 0 0.00% • unchanged
context_budget opencode_violations 0 0 0 0.00% • unchanged
context_budget claude_drop_rate 0 0 0 0.00% • unchanged
context_budget opencode_drop_rate 0 0 0 0.00% • unchanged
feedback claude_precision 1 1 0 0.00% • unchanged
feedback claude_recall 1 1 0 0.00% • unchanged
feedback claude_polarity_flips 0 0 0 0.00% • unchanged
feedback opencode_precision 1 1 0 0.00% • unchanged
feedback opencode_recall 1 1 0 0.00% • unchanged
feedback opencode_polarity_flips 0 0 0 0.00% • unchanged
memory claude_avg_body_chars 1278 1194 -84 -6.57% • unchanged
memory claude_name_format_violations 0 0 0 0.00% • unchanged
memory claude_secret_leakages 0 0 0 0.00% • unchanged

Plugin surface

No surface changes.

No regressions.

@itlackey itlackey merged commit 64c0846 into release/0.9.0 Jun 21, 2026
2 checks passed
@itlackey itlackey deleted the feat/0.9.0/session-end-extract branch June 21, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant